#pragma once
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

const int defaultfd = -1;

class UdpServer
{
public:
    // UdpServer(const std::string &ip, uint16_t port)
        //: _sockfd(defaultfd), _ip(ip), _port(port), _isrunning(false)
    UdpServer(uint16_t port)
        : _sockfd(defaultfd), _port(port), _isrunning(false)
    {
    }
    void Init()
    {
        // 1.创建socket套接字
        // 参数：地址族（IPv4地址），套接字类型（UDP），协议（默认为0）
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        // 创建成功：返回套接字描述符，创建失败：-1
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(1);
        }
        // 走到这里就创建成功了，接下来就是bind
        std::cout << "socket success, sockfd : " << _sockfd << std::endl;

        // 2. 将该本地的端口和ip与套接字描述符进行绑定
        // 参数：套接字描述符，通用地址结构(const struct sockaddr* addr)，地址结构的长度
        // 2.1 填充sockaddr_in结构体，再强转成sockaddr*类型进行传参
        struct sockaddr_in local;
        bzero(&local, 0);              // 初始化该结构体，避免填充字段的干扰，也可以使用memset，用到头文件<strings.h>
        local.sin_family = AF_INET;    // 地址族，IPv4
        local.sin_port = htons(_port); // 端口号，本地格式->网络字节序
        // ip也是如此，要将字符串转成4字节，再转成网络序列->inet_addr & inet_pton可以完成这两个任务
        
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY;
        
        // 2.2 开始bind
        // 返回值: 成功0，失败-1
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            std::cerr << "bind error" << std::endl;
            exit(2);
        }
        std::cout << "bind success, sockfd: " << _sockfd << std::endl;
    }
    void Start()
    {
        _isrunning = true;
        // 服务端一启动，就是一个死循环
        while (_isrunning)
        {
            // 1. 收消息
            // recvfrom参数: 套接字描述符，缓冲区地址和大小，标志位，sturct sockaddr* 结构体，存储结构体大小的地址
            char buffer[1024];
            struct sockaddr_in peer; // 输出型参数
            socklen_t len = sizeof(peer);
            // 返回值: 失败-1返回，成功返回实际读到的字节大小
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                // 2. 发消息
                // 先将缓冲区实际读到字节的后一个位置设置成\0
                buffer[n] = 0;
                std::cout << "buffer: " << buffer << std::endl; // 1. 消息内容 2. 谁发的(peer)
                // sendto参数: 前四个一致，后两个输入型参数
                std::string echo_string = "server echo@";
                echo_string += buffer;
                sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;     // 要有套接字描述符，可以将其初始化成默认值
    uint16_t _port;  // 存储本地的16位端口号，类似8080
    // std::string _ip; // 存储本地的IP地址，用的是字符串风格，点分十进制，类似->159.121.1.1
    bool _isrunning; // 表示该服务端的运行状态，默认是false
};